package org.luxor.commons.workflow.component.cmd;

import org.activiti.bpmn.model.*;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.HistoryService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.helper.TaskVariableCopier;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.deploy.DeploymentManager;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.luxor.commons.core.utils.UuidUtils;
import org.luxor.commons.workflow.component.mapper.CustomExecutionMapper;
import org.luxor.commons.workflow.constant.TransientVariableConst;
import org.luxor.commons.workflow.constant.VariableConst;
import org.luxor.commons.workflow.entity.CustomExecutionEntity;
import org.luxor.commons.workflow.utils.ProcessUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 任务驳回到上一步
 *
 * @author Mr.yan  @date 2022/8/24
 */
public class RejectPrevNodeCmd extends NeedsActiveTaskCmd<Void> {
    private static final long serialVersionUID = 1L;

    private final String collectionElementIndexVariable = "loopCounter";

    private Map<String, Object> variables;
    private Map<String, Object> transientVariables;
    private boolean localScope;

    private CommandContext commandContext;
    private TaskEntity taskEntity;

    /**
     * 任务驳回到上一步
     *
     * @param taskId 任务ID
     */
    public RejectPrevNodeCmd(String taskId) {
        this(taskId, null, null);
    }

    /**
     * 任务驳回到上一步
     *
     * @param taskId 任务ID
     */
    public RejectPrevNodeCmd(String taskId, Map<String, Object> variables) {
        this(taskId, variables, null);
    }

    /**
     * 任务驳回到上一步
     *
     * @param taskId             任务ID
     * @param variables          参数变量
     * @param transientVariables 瞬时变量
     */
    public RejectPrevNodeCmd(String taskId, Map<String, Object> variables, Map<String, Object> transientVariables) {
        super(taskId);
        this.variables = variables == null ? new HashMap<String, Object>() : variables;
        this.transientVariables = transientVariables == null ? new HashMap<String, Object>() : transientVariables;
    }

    /**
     * 任务驳回到上一步
     *
     * @param taskId     任务ID
     * @param variables  参数变量
     * @param localScope 参数变量仅当前任务可见？
     */
    public RejectPrevNodeCmd(String taskId, Map<String, Object> variables, boolean localScope) {
        super(taskId);
        this.variables = variables == null ? new HashMap<String, Object>() : variables;
        this.localScope = localScope;
    }

    @Override
    public Void execute(CommandContext commandContext, TaskEntity taskEntity) {
        this.commandContext = commandContext;
        this.taskEntity = taskEntity;

        // 获取上一步目标节点和办理人
        HistoricActivityInstance prevActivityInstance = getPrevActivityInstance();
        String targetActivityId = prevActivityInstance.getActivityId();
        String targetAssignee = prevActivityInstance.getAssignee();

        // 设置目标节点办理人
        transientVariables.put(TransientVariableConst.ASSIGNEE, targetAssignee);

        ProcessDefinitionCacheEntry processDefinitionCacheEntry = getProcessDefinitionCacheEntry(commandContext, taskEntity.getProcessDefinitionId());
        FlowElement targetUserTask = processDefinitionCacheEntry.getBpmnModel().getFlowElement(targetActivityId);
        if (targetUserTask == null) {
            throw new ActivitiException("未找到目标节点.");
        }
        if (!(targetUserTask instanceof UserTask)) {
            throw new ActivitiException("目标节点必须是用户活动类型.");
        }

        // 运行实例
        ExecutionEntity execution = taskEntity.getExecution();
        // 当前节点
        Activity sourceActivity = (Activity) processDefinitionCacheEntry.getBpmnModel().getFlowElement(taskEntity.getTaskDefinitionKey());
        // 目标节点
        UserTask targetActivity = (UserTask) targetUserTask;

        // 如果目标节点属于并行网关之内的节点，则收集并行网关内的[已办任务]
        List<FlowElement> parallelGatewayOutgoingFlow = getParallelGatewayIncomingFlow(sourceActivity);
        if (!CollectionUtils.isEmpty(parallelGatewayOutgoingFlow)) {
            for (FlowElement parallelGateway : parallelGatewayOutgoingFlow) {
                // 查询有几个已流转的任务
                List<HistoricActivityInstance> historicActivityInstanceList = commandContext.getProcessEngineConfiguration()
                        .getHistoryService().createHistoricActivityInstanceQuery()
                        .processInstanceId(taskEntity.getProcessInstanceId())
                        .activityId(parallelGateway.getId())
                        .list();
                if (historicActivityInstanceList.size() > 0) {
                    historicActivityInstanceList.remove(0);
                }
                for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
                    CustomExecutionEntity customExecution = new CustomExecutionEntity();
                    customExecution.setId(UuidUtils.uuid());
                    customExecution.setProcessInstanceId(historicActivityInstance.getProcessInstanceId());
                    customExecution.setParentId(historicActivityInstance.getProcessInstanceId());
                    customExecution.setProcessDefinitionId(historicActivityInstance.getProcessDefinitionId());
                    customExecution.setRootProcessInstanceId(historicActivityInstance.getProcessInstanceId());
                    customExecution.setActivityId(historicActivityInstance.getActivityId());
                    customExecution.setActive(false);
                    customExecution.setTenantId(historicActivityInstance.getTenantId());
                    customExecution.setStartTime(historicActivityInstance.getStartTime());
                    commandContext.getDbSqlSession().getCustomMapper(CustomExecutionMapper.class).createExecution(customExecution);
                }
            }
        }

        // 删除待办任务
        List<Activity> childActivityList = ProcessUtils.childActivityList(taskEntity.getProcessDefinitionId(), targetActivity.getId());
        List<TaskEntity> ruTaskEntityList = commandContext.getTaskEntityManager().findTasksByProcessInstanceId(taskEntity.getProcessInstanceId());
        for (TaskEntity ruTaskEntity : ruTaskEntityList) {
            for (Activity childActivity : childActivityList) {
                if (ruTaskEntity.getTaskDefinitionKey().equals(childActivity.getId())) {
                    commandContext.getTaskEntityManager().delete(ruTaskEntity.getId());
                    commandContext.getIdentityLinkEntityManager().deleteIdentityLinksByTaskId(ruTaskEntity.getId());
                    if (!execution.equals(ruTaskEntity.getExecution())) {
                        commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(ruTaskEntity.getExecution(), "jump to:" + targetActivityId, false);
                    }
                    commandContext.getHistoryManager().recordTaskEnd(ruTaskEntity.getId(), "jump to:" + targetActivityId);
                    commandContext.getHistoryManager().recordActivityEnd(ruTaskEntity.getExecution(), "jump to:" + targetActivityId);
                }
            }
        }

        // 多实例节点跳出
        if (sourceActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            execution = getMultiInstanceRootExecution(taskEntity.getExecution());
            removeLocalLoopVariable(execution, collectionElementIndexVariable);
            execution.setActive(true);
            execution.setMultiInstanceRoot(false);
            commandContext.getExecutionEntityManager().update(execution);
        }

        // 从子流跳到父流程
        if (sourceActivity.getParentContainer() instanceof SubProcess && execution.getParent() != null) {
            String subProcessId = ((SubProcess) sourceActivity.getParentContainer()).getId();
            Map<String, FlowElement> flowElementMap = targetActivity.getParentContainer().getFlowElementMap();
            if (flowElementMap != null && flowElementMap.get(subProcessId) != null) {
                execution = execution.getParent();
            }
        }

        // 从父流程跳回到子流程
        if (targetActivity.getParentContainer() instanceof SubProcess) {
            SubProcess subProcess = (SubProcess) targetActivity.getParentContainer();
            Map<String, FlowElement> flowElementMap = sourceActivity.getParentContainer().getFlowElementMap();
            if (flowElementMap.get(subProcess.getId()) != null) {
                FlowElement subStartEvents = subProcess.getFlowElements().stream().filter(el -> el instanceof StartEvent).findFirst().orElse(null);
                if (subStartEvents == null) {
                    throw new ActivitiException("No initial activity found for subprocess " + subProcess.getId());
                }
                execution.setScope(true);
                execution = Context.getCommandContext().getExecutionEntityManager().createChildExecution(execution);
            }
        }

        // 设置参数
        if (targetActivity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            Object assignee = transientVariables.get(TransientVariableConst.ASSIGNEE);
            if (assignee != null && variables.get(VariableConst.ASSIGNEE_LIST) == null) {
                List<String> assigneeList = new ArrayList<>();
                assigneeList.add((String) assignee);
                variables.put(VariableConst.ASSIGNEE_LIST, assigneeList);
            }
        }
        setVariables(execution);

        // 触发实例运转
        execution.setCurrentFlowElement(targetActivity);
        commandContext.getAgenda().planContinueProcessInCompensation(execution);
        return null;
    }

    private HistoricActivityInstance getPrevActivityInstance() {
        HistoryService historyService = commandContext.getProcessEngineConfiguration().getHistoryService();
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(taskEntity.getProcessInstanceId()).activityType("userTask").finished()
                .orderByHistoricActivityInstanceEndTime().desc().list();
        if (CollectionUtils.isEmpty(historicActivityInstanceList)) {
            throw new ActivitiObjectNotFoundException("找不到上一步，流程节点记录");
        }

        List<Activity> prevActivityList = ProcessUtils.prevActivityList(taskEntity.getProcessDefinitionId(), taskEntity.getTaskDefinitionKey());
        List<String> prevActivityIds = prevActivityList.stream().map(Activity::getId).collect(Collectors.toList());

        HistoricActivityInstance prevActivityInstance = null;
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            if (CollectionUtils.contains(prevActivityIds.iterator(), historicActivityInstance.getActivityId())) {
                prevActivityInstance = historicActivityInstance;
                break;
            }
        }
        if (prevActivityInstance == null || StringUtils.isEmpty(prevActivityInstance.getActivityId())) {
            throw new ActivitiObjectNotFoundException("找不到上一步，流程节点记录");
        }
        if (StringUtils.isEmpty(prevActivityInstance.getAssignee())) {
            throw new ActivitiObjectNotFoundException("找不到上一步，办理人记录");
        }
        return prevActivityInstance;
    }

    private ProcessDefinitionCacheEntry getProcessDefinitionCacheEntry(CommandContext commandContext, String processDefinitionId) {
        DeploymentManager deploymentManager = commandContext.getProcessEngineConfiguration().getDeploymentManager();
        return deploymentManager.getProcessDefinitionCache().get(processDefinitionId);
    }

    private List<FlowElement> getParallelGatewayIncomingFlow(Activity sourceActivity) {
        List<FlowElement> forkParallelGatewayList = sourceActivity.getIncomingFlows().stream()
                .map(SequenceFlow::getSourceFlowElement).filter(flowElement -> flowElement instanceof ParallelGateway).collect(Collectors.toList());
        return forkParallelGatewayList;
    }

    private void removeLocalLoopVariable(DelegateExecution execution, String variableName) {
        execution.removeVariableLocal(variableName);
    }

    private void setVariables(ExecutionEntity execution) {
        if (variables != null) {
            if (localScope) {
                execution.setVariablesLocal(variables);
            } else {
                execution.setVariables(variables);
            }
        }
        if (transientVariables != null) {
            if (localScope) {
                execution.setTransientVariablesLocal(transientVariables);
            } else {
                execution.setTransientVariables(transientVariables);
            }
        }
        if (commandContext.getProcessEngineConfiguration().isCopyVariablesToLocalForTasks()) {
            TaskVariableCopier.copyVariablesOutFromTaskLocal(taskEntity);
        }
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }
}
